<!--
 * @Description: ------------ fileDescription -----------
 * @Author: snows_l snows_l@163.com
 * @Date: 2024-08-31 12:42:12
 * @LastEditors: snows_l snows_l@163.com
 * @LastEditTime: 2025-11-05 13:02:34
 * @FilePath: \BLOG\src\components\common\CommentView.vue
-->
<!--
  * @Description: 评论展示组件
  *props:
  *  articleId: 文章id
  *  type: 0：留言  1 文章 2:友联 3: 设备
  *  list: 评论列表
  *emits:
  *  submit: 评论提交
 -->
<template>
  <div class="comment-view-container" id="comment-view-container" :class="{ 'm-comment-view-container': isMobi }">
    <div class="max-width-center">
      <p class="shengming" style="font-size: 16px; margin-bottom: 20px; line-height: 24px">
        声明：评论区均会
        <span style="color: red">“通过敏感词检测”</span>
        并且由站长
        <span style="color: red">“审核通过”</span>
        之后再进行展示。如有不当言论，请联系站长进行删除处理。
      </p>
      <div class="comment-view-content" :style="{ marginBottom: state.page.total <= state.page.size ? '30px' : '0px' }" v-if="state.list && state.list.length > 0">
        <div class="comment-item-wrap intersection-observer" v-for="item in state.list" :key="item.id">
          <div class="msg-item-bg"></div>
          <div class="comment-item">
            <div class="comment-item-inner">
              <div class="info-wrap">
                <div class="avatar-wrap">
                  <img :src="getQQAvatar(item.qq)" />
                </div>
                <div class="info">
                  <div class="nick-name">
                    <span style="margin-right: 4px">{{ item.nickName }}</span>
                    <div class="bozhu" v-if="item.qq === '37523953'">站长</div>
                    <div
                      class="bozhu level"
                      :style="{ color: item.qq === '37523953' ? '#ff6600' : '#d3a833', border: item.qq === '37523953' ? '1px solid #ff6600' : '1px solid #d3a833' }"
                      style="margin-left: 5px; margin-right: 5px">
                      Lv{{ item.qq == '37523953' ? '10' : '2' }}
                    </div>
                  </div>
                  <div class="time-add">
                    <span class="">{{ item.time ? moment(item.time).format('YYYY年MM月DD日 HH:mm') : '' }}</span>
                    <span style="margin-left: 20px">归属地：{{ item.city || '未知' }}</span>
                    <span style="margin-left: 20px" v-if="item.websiteUrl">
                      个人小站：
                      <span @click="handleToWebsite(item.websiteUrl)" class="link pointer kbn-custom" :data-tip="'是否前往 「' + item.nickName + '」 的小站吗？'">
                        {{ item.websiteUrl || '未知' }}
                      </span>
                    </span>
                  </div>
                </div>
              </div>
              <div class="msg-content-wrap">
                <div class="content text">
                  <div class="comment" v-for="text in item.comments">{{ text }}</div>
                </div>
                <div class="operation-wrap">
                  <div class="like-wrap">
                    <IconLike :size="18" :animation="item.isGood" class="iconfont pointer" @click="handleGood(item)"></IconLike>
                    <span class="like-value">{{ item.likeCount || 0 }}</span>
                  </div>
                  <div class="comment-wrap pointer" @click="handleComment(item.id, item)">
                    <img class="comment-icon" src="@/assets/images/icon/icon_comment.svg" alt="" />
                    <span class="comment-value">{{ (item.children && item.children.length) || 0 }}</span>
                  </div>
                </div>
              </div>
              <div class="sub-comment" v-if="item.children && item.children.length > 0">
                <div class="sub-padding">
                  <div class="sub-comment-item" v-for="subItem in item.children" :key="subItem.id">
                    <div class="title">
                      <img class="avatar" :src="getQQAvatar(subItem.qq)" alt="" srcset="" />
                      <span>{{ subItem.nickName }}</span>
                      <div class="bozhu" v-if="subItem.qq === '37523953'">站长</div>
                      <span style="margin: 0 6px; color: var(--theme-light-color-4)">回复</span>
                      <span style="margin-right: 20px">{{ subItem.toNickName }}</span>
                      <span class="">{{ subItem.time ? moment(subItem.time).format('YYYY年MM月DD日 HH:mm') : '' }}</span>
                    </div>
                    <div class="sub-msg-content-wrap">
                      <div class="sub-content text">
                        <div class="comment" v-for="item in subItem.comments">{{ item }}</div>
                      </div>
                      <div class="operation-wrap">
                        <div class="like-wrap">
                          <IconLike :size="18" :animation="subItem.isGood" class="iconfont pointer" @click="handleGood(subItem)"></IconLike>
                          <span class="like-value">{{ subItem.likeCount || 0 }}</span>
                        </div>
                        <div class="comment-wrap pointer" @click="handleComment(item.id, subItem)">
                          <img class="comment-icon" src="@/assets/images/icon/icon_comment.svg" alt="" />
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div v-else class="no-comment">
        <Empty :text="'暂无评论数据，期待您的评论~'" :loadingText="'评论正在拼命加载中...'"></Empty>
      </div>

      <div class="botom-wrap" style="display: flex; justify-content: center; align-items: center">
        <LoadingMore
          v-if="store.projectConfig.pageType == 'more'"
          :loading="state.loading"
          :hasMore="state.isMore"
          :length="state.list.length"
          @loadMore="handleLoadMore"></LoadingMore>
        <CustomPage v-else :page="state.page" @change="handlePageChange" :loading="state.loading" scorllTop="max-width-center"></CustomPage>
      </div>

      <div v-if="isShowEnter" class="input-wrap intersection-observer" ref="commentInputRef">
        <div class="ttt">
          <div class="hui-warp">
            <div class="input-title" v-if="(type == 1 ? articleId != state.form.pId : true) && state.form.pId">
              ⚠️ 回复：
              <span :style="{ color: 'var(--theme-color)' }">{{ state.form.toNickName }}</span>
              <div class="bozhu" v-if="state.form.toQQ === '37523953'">站长</div>
            </div>
          </div>
          <div class="opertion-wrap">
            <div v-if="type == 2" class="back-title back-me pointer" @click="handleFriendLinkTemp">友联模板</div>
            <div class="back-title back-me pointer" @click="handleDaka">打 卡</div>
            <div class="back-title back-me pointer" @click="clearTO">{{ commentLabel }}</div>
          </div>
        </div>
        <CommentInput v-model="state.comment" @submit="handleSubmit"></CommentInput>
      </div>
      <div v-else class="no-input">⚠️ 站长暂时关闭评论功能，请谅解！</div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { getDict } from '@/api/common';
import { addComment, addLike, getCommentList2 } from '@/api/comment';
import useIntersectionObserver from '@/hooks/useIntersectionObserver';
import useResize from '@/hooks/useResize';
import { useAppStore } from '@/store/app';
import { CustomMessage, getQQAvatar, getTime, tranListToTree } from '@/utils/common';
import { live2dReloadElement } from '@/utils/live2d';
import { nextTick, onUpdated, reactive, ref, watch, computed } from 'vue';
import moment from 'moment';
const { isMobi } = useResize();

const store = useAppStore();
useIntersectionObserver();

const props = defineProps({
  articleId: {
    type: Number,
    default: 0
  },
  // 0:留言 1:文章评论 2：友联评论 3:设备评论 4:评论碎碎念
  type: {
    type: [String, Number],
    default: '1'
  }
});

const emits = defineEmits(['submit']);

const commentInputRef = ref(null);
let isShowEnter = ref(store.projectConfig.showEntry == true || store.projectConfig.showEntry == 'true' || false);

const commentLabel = computed(() => {
  return state.commentTypeMap[props.type] || '留言';
});

const state = reactive({
  loading: false,
  isMore: true,
  page: {
    page: 1,
    size: 5,
    total: 0
  },
  list: [],
  commentTypeMap: {
    // 1: '评论文章',
    // 2: '友联留言',
    // 3: '评论设备',
    // 4: '评论碎碎念'
  },
  comment: {
    value: '',
    qq: '',
    nickName: '',
    avatarUrl: '',
    email: '',
    websiteUrl: '',
    isPrivacy: false,
    isEmailFeekback: true,
    submitLoading: false
  },
  form: {
    pId: 0,
    toId: 0,
    toNickName: ''
  }
});

// 获取音乐类型
const getCommentTypeFn = () => {
  getDict({ dictType: 'comment_type' }).then((res: any) => {
    if (res.code == 200) {
      state.commentTypeMap = {
        time: moment().format('YYYY-MM-DD')
      };
      res.data.forEach((item: any) => {
        if (item.value) state.commentTypeMap[item.value] = item.remark || '';
      });
      store.SET_COMMENT_TYPE_DICT(state.commentTypeMap);
    }
  });
};

// 如果今天已经请求了就不再请求了
if (!store.commentTypeMap?.time || store.commentTypeMap?.time != moment().format('YYYY-MM-DD')) {
  getCommentTypeFn();
} else {
  state.commentTypeMap = store.commentTypeMap || {};
}
// 获取评论列表
const handleGetCommentFn = (scorllTopCallback: Function | boolean) => {
  state.loading = true;
  let params = {
    ...state.page,
    type: props.type,
    articleId: props.type == 1 ? props.articleId : 0
  };
  getCommentList2(params)
    .then(res => {
      if (res.code == 200) {
        res.data.forEach(item => {
          item.comments = item.comment.split('\n');
        });
        if (store.projectConfig.pageType == 'more') {
          state.list = tranListToTree([...state.list, ...res.data], 'id', 'pId', 'children');
        } else {
          state.list = tranListToTree(res.data, 'id', 'pId', 'children');
        }
        state.page.total = res.total;
        if (state.list.length >= res.total) {
          state.isMore = false; // 已经没有更多数据了
        }
        scorllTopCallback && scorllTopCallback();
      }
    })
    .catch(() => {})
    .finally(() => {
      state.loading = false;
    });
};

// 加在更多
const handleLoadMore = () => {
  state.page.page++;
  handleGetCommentFn(false);
};

const handlePageChange = (page: number, size: number, scorllTopCallback: Function) => {
  state.page.page = page;
  state.page.size = size;
  handleGetCommentFn(scorllTopCallback);
};

// 点赞
const handleGood = row => {
  addLike({ id: row.id }).then(res => {
    row.isGood = true;
    if (res.code === 200) {
      row.likeCount = res.data;
      setTimeout(() => {
        row.isGood = false;
      }, 500);
    }
  });
};

// 滚动到底部留言并且然输入框聚焦
const handleScrollToBottom = () => {
  nextTick(() => {
    let top = 0;
    const layoutEle: Element = document.querySelector('#layout') as Element;
    const commentInputEle: Element = document.querySelector('.comment-value-inner') as Element;
    if (commentInputRef.value) {
      top = (commentInputRef.value as Element).offsetTop - 70;
      layoutEle.scrollTo({ top, behavior: 'smooth' });
    }
    if (commentInputEle) commentInputEle.focus();
  });
};

// 处理点击评论
const handleComment = (pid, row) => {
  state.form.pId = pid;
  state.form.toId = row.id;
  state.form.toQQ = row.qq;
  state.form.toNickName = row.nickName;
  handleScrollToBottom();
};

// 滚动到评论区顶部
const handleScorllTop = () => {
  nextTick(() => {
    const layout = document.querySelector('#layout');
    const topEle = document.querySelector('#comment-view-container');
    const top = (topEle as HTMLElement).offsetTop - 60;
    (layout as HTMLElement).scrollTo({ top, behavior: 'smooth' });
  });
};

// 提交评论
const handleSubmit = () => {
  let params = {
    ...state.comment,
    comment: state.comment.value,
    isPrivacy: state.comment.isPrivacy ? 1 : 0,
    isEmailFeekback: state.comment.isEmailFeekback ? 1 : 0,
    ...state.form,
    type: props.type,
    articleId: props.type == 1 ? props.articleId : 0
  };
  addComment(params)
    .then(res => {
      if (res.code === 200) {
        state.form.pId = 0;
        state.form.toNickName = '';
        state.comment.value = '';
        state.form.toId = 0;
        state.form.pId = 0;
        state.form.toNickName = '';
        state.comment.submitLoading = false;
        CustomMessage({ msg: '评论成功, 站长会在24小时内审核', type: 'success' });
        handleGetCommentFn(handleScorllTop);
        emits('submit');
      } else {
        state.comment.submitLoading = false;
      }
    })
    .finally(() => {
      state.comment.submitLoading = false;
    });
};

// 点击给我留言
const clearTO = () => {
  state.form.pId = 0;
  state.form.toId = 0;
  state.form.toNickName = '';
  handleScrollToBottom();
};

// 点击个人小站
const handleToWebsite = url => {
  if (!url.startsWith('http')) url = 'http://' + url;
  window.open(url, '_blank');
};

// 设置默认值
const setDefaultValue = (payload = {}) => {
  let paramskeys = Object.keys(payload);
  for (let key of paramskeys) {
    state.comment[key] = payload[key] ? payload[key] : '';
  }
};

// 打卡
const handleDaka = () => {
  const text = `滴！学生卡。-- ${moment().format('YYYY-MM-DD HH:mm:ss')} `;
  setDefaultValue({ value: text });
  const commentInputEle: Element = document.querySelector('.comment-value-inner') as Element;
  if (commentInputEle) commentInputEle.focus();
};

// 快速回复友链模板
const handleFriendLinkTemp = () => {
  const linkTemp = `名称：
简介：
地址：
LOGO/QQ：`;
  setDefaultValue({ value: linkTemp });
  const commentInputEle: Element = document.querySelector('.comment-value-inner') as Element;
  if (commentInputEle) commentInputEle.focus();
};

onUpdated(() => {
  live2dReloadElement();
});

defineExpose({
  clearTO,
  setDefaultValue,
  handleScrollToBottom
});

handleGetCommentFn();

watch(
  () => props.articleId && props.articleId,
  n => {
    if (n) handleGetCommentFn();
  },
  { immediate: true }
);
</script>

<style lang="scss" scoped>
.comment-view-container {
  padding-top: 40px;
  padding-bottom: 20px;
  width: 100%;
  .max-width-center {
    border-radius: var(--border-radius-2);
    height: 100%;
    max-width: var(--content-max-width);
    margin: 0 auto;
    .shengming {
      text-shadow: var(--text-shadow);
    }
    .comment-view-content {
      .comment-item-wrap {
        position: relative;
        margin: 15px 0;
        .msg-item-bg {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          background-color: var(--theme-light-color-5);
          z-index: -1;
          // 旋转
          transform: rotate(-1deg);
        }
        .comment-item {
          padding: 15px;
          background-color: var(--msg-bg-color);
          .comment-item-inner {
            padding: 0px 20px 10px;
            border: 1px dashed var(--border-color);
          }
          .info-wrap {
            display: flex;
            padding: 10px 0;
            align-items: space-around;
            .avatar-wrap {
              width: 40px;
              height: 40px;
              border-radius: 5px;
              overflow: hidden;
              flex-shrink: 0;
              img {
                width: 100%;
                height: 100%;
              }
            }
            .info {
              margin-left: 10px;
              display: flex;
              flex-direction: column;
              justify-content: space-around;
              font-size: 12px;
              .nick-name {
                display: flex;
                align-items: center;
                margin-bottom: 4px;

                .level {
                  border: 1px solid #ddd;
                  color: var(--text-color);
                  font-family: 优设标题黑 !important;
                }
              }
            }
          }
          .msg-content-wrap {
            // padding-left: 50px;
          }
          .content {
            font-size: 14px;
            color: var(--text-color);
            word-break: break-all;
            background-image: url('@/assets/images/bg/wordline.webp');
            line-height: 40px;
            min-height: 40px;
          }
          .operation-wrap {
            padding: 10px 0;
            display: flex;
            justify-content: flex-start;
            align-items: center;
            .like-wrap {
              display: flex;
              align-items: center;
              .like-value {
                margin-left: 8px;
                display: inline-block;
                min-width: 15px;
              }
            }
            .comment-wrap {
              margin-left: 10px;
              display: flex;
              align-items: center;
              .comment-icon {
                width: 19px;
              }
              .comment-value {
                margin-left: 8px;
              }
            }
          }
          .sub-comment {
            padding-left: 20px;
            .sub-padding {
              border-radius: 5px;
              border-radius: var(--border-radius-1);
              background-color: var(--sub-msg-bg-color);
              padding: 5px 0px 5px;
              .sub-comment-item {
                padding: 5px 10px;
                margin: 5px 0;
                .title {
                  font-size: 12px;
                  line-height: 20px;
                  padding-bottom: 10px;
                  display: flex;
                  flex-wrap: wrap;
                  align-items: center;
                }
                .avatar {
                  width: 30px;
                  height: 30px;
                  border-radius: 5px;
                  margin-right: 10px;
                }
                .sub-msg-content-wrap {
                  // padding-left: 40px;
                  .sub-content {
                    font-size: 14px;
                    color: var(--text-color);
                    word-break: break-all;
                    background-image: url('@/assets/images/bg/wordline.webp');
                    line-height: 40px;
                    min-height: 40px;
                  }
                }
              }
            }
          }
        }
      }
    }
    .input-wrap {
      margin-top: 20px;
      .input-title {
        font-size: 16px;
        font-weight: 600;
        line-height: 40px;
        display: flex;
        align-items: center;
      }
    }
  }
}
.m-comment-view-container {
  width: var(--content-max-width-m);
  margin: 0 auto;
  padding-top: 20px !important;
  padding-bottom: 15px;
  .comment-item-wrap {
    margin: 10px 0 !important;
    .comment-item {
      padding: 5px !important;
      .comment-item-inner {
        padding: 0px 10px 10px !important;
      }
    }
  }
}
.ttt {
  padding: 0 0.3125rem;
  height: 3rem;
  display: flex;
  justify-content: space-between;
  .back-title {
    font-size: 0.875rem;
    line-height: 2.5rem;
    text-shadow: var(--text-shadow);
  }
  .opertion-wrap {
    display: flex;
    align-items: center;
    .back-me {
      margin-left: 20px;
      border: 1px solid var(--border-color);
      border-radius: 26px;
      padding: 0 8px;
      height: 26px;
      line-height: 26px;
      transition: all 0.6s ease;
      &:hover {
        border-color: var(--theme-color);
        background-color: var(--theme-light-color-3);
        color: #fff;
      }
    }
  }
}
.bozhu {
  padding: 0px 5px;
  font-size: 12px;
  border: 1px solid var(--theme-light-color-5);
  color: var(--theme-color);
  margin: 0 5px;
  border-radius: 5px;
  line-height: 20px;
}
.no-input {
  height: 120px;
  border-radius: var(--border-radius-2);
  background-color: var(--bg-content-color);
  color: var(--empty-text-color);
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 18px;
  font-weight: 600;
}
</style>
